﻿using System;
using System.Collections.Generic;
using CLIFile;
using CompileLib.InteropServices.NVIDIA;
using CompileLib.InteropServices;
using PerformanceTest.GPU.NVIDIA;
using PerformanceTest.GPU;

namespace Scheduling
{
    public enum Platform { NVIDIA, ATI };

    public class GPGPUPerfModel
    {
        public virtual long WGCost(long wgSize, Dictionary<OPCODES, long> ic)
        {
            return 0;
        }

        public virtual int ComputeUnits()
        {
            return 0;
        }

        public virtual int WorkGroupSize()
        {
            return 0;
        }

        public virtual float GetH2DTransferTime(long bytes)
        {
            return 0.0f;
        }

        public virtual float GetD2HTransferTime(long bytes)
        {
            return 0.0f;
        }

        public long KernelCost(long doeSize, long wgSize, int cu, Dictionary<OPCODES, long> ic)
        {
            long nwg = doeSize / wgSize;
            return WGCost(wgSize, ic) * nwg / cu;
        }

        public long GetSingleKernelCost(
            long doeSize, 
            long inputBytes, 
            long outputBytes, 
            Dictionary<OPCODES, long> ic, 
            double unit)
        {
            long h2d = 0;
            long d2h = 0;
            long setup = 0;
            long kernelCost = 0;
            float h2dtimeInMilliseconds = 0.0f;
            float d2htimeInMilliseconds = 0.0f;
            h2dtimeInMilliseconds = GetH2DTransferTime(inputBytes);
            d2htimeInMilliseconds = GetD2HTransferTime(outputBytes);
            h2d = (long)(h2dtimeInMilliseconds / unit);
            d2h = (long)(d2htimeInMilliseconds / unit);
            Console.WriteLine("H2D: {0}", h2d);
            Console.WriteLine("D2H: {0}", d2h);
            Console.WriteLine("TOTAL TRANSFER: {0}", d2h + h2d);
            
            kernelCost = KernelCost(doeSize, WorkGroupSize(), ComputeUnits(), ic);
            Console.WriteLine("Kernel: {0}", kernelCost);
            return setup + h2d + d2h + kernelCost;
        }
    }

    public class NVIDIAPerfModel : GPGPUPerfModel
    {
        private Dictionary<OPCODES, long> NVIDIACostModel = new Dictionary<OPCODES, long>();

        private NVIDIAPerformance perf;

        public override long WGCost(long wgSize, Dictionary<OPCODES, long> ic)
        {
            long warpSize = CUDA.GetWarpSize(0);
            long nwarp = wgSize / warpSize;
            long cost = 0;
            foreach (OPCODES op in ic.Keys)
            {
                cost += ic[op] * NVIDIACostModel[op];
            }
            return cost * nwarp;
        }

        public override float GetH2DTransferTime(long bytes)
        {
            return perf.GetH2DTransferTime(bytes);
        }

        public override float GetD2HTransferTime(long bytes)
        {
            return perf.GetD2HTransferTime(bytes);
        }

        public override int ComputeUnits()
        {
            return CUDA.GetSMCount(0);
        }

        public override int WorkGroupSize()
        {
            return 256;
        }

        public NVIDIAPerfModel() : base()
        {
            perf = new NVIDIAPerformance(0);
            perf.TestPerformance();

            NVIDIACostModel.Add(OPCODES.Add, 4);
            NVIDIACostModel.Add(OPCODES.Sub, 4);
            NVIDIACostModel.Add(OPCODES.Mul, 4);
            NVIDIACostModel.Add(OPCODES.Div, 5);
            NVIDIACostModel.Add(OPCODES.Br, 1);
            NVIDIACostModel.Add(OPCODES.Br_S, 1);
            NVIDIACostModel.Add(OPCODES.Brfalse, 1);
            NVIDIACostModel.Add(OPCODES.Brfalse_S, 1);
            NVIDIACostModel.Add(OPCODES.Brtrue, 1);
            NVIDIACostModel.Add(OPCODES.Brtrue_S, 1);
            NVIDIACostModel.Add(OPCODES.Call, 8);
            NVIDIACostModel.Add(OPCODES.Calli, 8);
            NVIDIACostModel.Add(OPCODES.Callvirt, 8);
            NVIDIACostModel.Add(OPCODES.Ldarg, 2);
            NVIDIACostModel.Add(OPCODES.Ldarg_0, 2);
            NVIDIACostModel.Add(OPCODES.Ldarg_1, 2);
            NVIDIACostModel.Add(OPCODES.Ldarg_2, 2);
            NVIDIACostModel.Add(OPCODES.Ldarg_3, 2);
            NVIDIACostModel.Add(OPCODES.Ldarg_S, 2);
            NVIDIACostModel.Add(OPCODES.Starg, 2);
            NVIDIACostModel.Add(OPCODES.Starg_S,2);
            NVIDIACostModel.Add(OPCODES.Ldloc, 2);
            NVIDIACostModel.Add(OPCODES.Ldloc_0, 2);
            NVIDIACostModel.Add(OPCODES.Ldloc_1, 2);
            NVIDIACostModel.Add(OPCODES.Ldloc_2, 2);
            NVIDIACostModel.Add(OPCODES.Ldloc_3, 2);
            NVIDIACostModel.Add(OPCODES.Ldloc_S, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4_0, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4_1, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4_2, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4_3, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4_4, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4_5, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4_6, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4_7, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4_8, 2);
            NVIDIACostModel.Add(OPCODES.Ldc_I4_S, 2);
            NVIDIACostModel.Add(OPCODES.Stloc, 2);
            NVIDIACostModel.Add(OPCODES.Stloc_0, 2);
            NVIDIACostModel.Add(OPCODES.Stloc_1, 2);
            NVIDIACostModel.Add(OPCODES.Stloc_2, 2);
            NVIDIACostModel.Add(OPCODES.Stloc_3, 2);
            NVIDIACostModel.Add(OPCODES.Stloc_S, 2);
            NVIDIACostModel.Add(OPCODES.Clt, 1);
            NVIDIACostModel.Add(OPCODES.Nop, 0);
            NVIDIACostModel.Add(OPCODES.Ret, 0);
        }
    }

    public class CPUPerfModel
    {
        private Dictionary<OPCODES, long> CPUCostModel = new Dictionary<OPCODES, long>();

        public long ExecutionCost(Dictionary<OPCODES, long> ic)
        {
            long cost = 0;
            foreach (OPCODES op in ic.Keys)
            {
                cost += ic[op] * CPUCostModel[op];
            }
            return cost;
        }

        public CPUPerfModel()
        {
            CPUCostModel.Add(OPCODES.Add, 4);
            CPUCostModel.Add(OPCODES.Sub, 4);
            CPUCostModel.Add(OPCODES.Mul, 4);
            CPUCostModel.Add(OPCODES.Div, 5);
            CPUCostModel.Add(OPCODES.Br, 1);
            CPUCostModel.Add(OPCODES.Br_S, 1);
            CPUCostModel.Add(OPCODES.Brfalse, 1);
            CPUCostModel.Add(OPCODES.Brfalse_S, 1);
            CPUCostModel.Add(OPCODES.Brtrue, 1);
            CPUCostModel.Add(OPCODES.Brtrue_S, 1);
            CPUCostModel.Add(OPCODES.Call, 13);
            CPUCostModel.Add(OPCODES.Calli, 13);
            CPUCostModel.Add(OPCODES.Callvirt, 13);
            CPUCostModel.Add(OPCODES.Ldarg, 2);
            CPUCostModel.Add(OPCODES.Ldarg_0, 2);
            CPUCostModel.Add(OPCODES.Ldarg_1, 2);
            CPUCostModel.Add(OPCODES.Ldarg_2, 2);
            CPUCostModel.Add(OPCODES.Ldarg_3, 2);
            CPUCostModel.Add(OPCODES.Ldarg_S, 2);
            CPUCostModel.Add(OPCODES.Starg, 2);
            CPUCostModel.Add(OPCODES.Starg_S, 2);
            CPUCostModel.Add(OPCODES.Ldloc, 2);
            CPUCostModel.Add(OPCODES.Ldloc_0, 2);
            CPUCostModel.Add(OPCODES.Ldloc_1, 2);
            CPUCostModel.Add(OPCODES.Ldloc_2, 2);
            CPUCostModel.Add(OPCODES.Ldloc_3, 2);
            CPUCostModel.Add(OPCODES.Ldloc_S, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4_0, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4_1, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4_2, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4_3, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4_4, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4_5, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4_6, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4_7, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4_8, 2);
            CPUCostModel.Add(OPCODES.Ldc_I4_S, 2);
            CPUCostModel.Add(OPCODES.Stloc, 2);
            CPUCostModel.Add(OPCODES.Stloc_0, 2);
            CPUCostModel.Add(OPCODES.Stloc_1, 2);
            CPUCostModel.Add(OPCODES.Stloc_2, 2);
            CPUCostModel.Add(OPCODES.Stloc_3, 2);
            CPUCostModel.Add(OPCODES.Stloc_S, 2);
            CPUCostModel.Add(OPCODES.Clt, 1);
            CPUCostModel.Add(OPCODES.Nop, 0);
            CPUCostModel.Add(OPCODES.Ret, 0);
        }
    }
}